package com.dix.codec.bkv.app;

import com.dix.codec.bkv.BKV;
import com.dix.codec.bkv.CodecUtil;
import com.dix.codec.bkv.UnpackBKVResult;

import java.util.Arrays;

public class CoreParser {
    private static final String LogTag = "core-parser";
    private static byte[] remainingBuf = new byte[0];

    public static void parse(byte[] newBuf) {
        byte[] buf = concat(remainingBuf, newBuf);
        while (true) {
            if (buf.length == 0) {
                break;
            }

            ParseResult parseResult = process(buf);
            switch (parseResult.result) {
                case ParseResult.ResultIncomplete:
                remainingBuf = parseResult.buf;
                return;
                case ParseResult.ResultInvalid:
                buf = Arrays.copyOfRange(buf, 1, buf.length);
                break;
                case ParseResult.ResultFatal:
                buf = new byte[0];
                break;
                case ParseResult.ResultOK:
                buf = parseResult.buf;
                log("parse out bkv:");
                parseResult.bkv.dump();
                break;
            }
        }

        remainingBuf = buf;
    }

    public static ParseResult process(byte[] buf) {
        log("parsing: " + CodecUtil.bytesToHex(buf));

        if (buf.length < 4) {
            log("buffer too short");
            return new ParseResult(ParseResult.ResultIncomplete, buf, null);
        }

        int headIndex = indexOf(buf, new byte[]{(byte) 0xFF});
        if (headIndex < 0) {
            log("header not exists");
            return new ParseResult(ParseResult.ResultFatal, null, null);
        }

        byte[] pb = Arrays.copyOfRange(buf, headIndex, buf.length);
        if (pb.length < 3) {
            log("frame too short");
            return new ParseResult(ParseResult.ResultIncomplete, buf, null);
        }

        int length = pb[1];
        if (pb.length < length + 2) {
            log("frame length not match, size=" + pb.length + ", length=" + length);
            return new ParseResult(ParseResult.ResultIncomplete, buf, null);
        }

        byte[] bb = Arrays.copyOfRange(pb, 3, length + 2);

        byte checksum = calculateChecksum(bb);
        if (checksum != pb[2]) {
            log("checksum not match, calculateChecksum=" + checksum + ", checksum=" + pb[2]);
            return new ParseResult(ParseResult.ResultInvalid, buf, null);
        }

        UnpackBKVResult unpack = BKV.unpack(bb);
        if (unpack.getBKV().getItems().isEmpty()) {
            log("unpack bkv fail, empty items");
            return new ParseResult(ParseResult.ResultInvalid, buf, null);
        }

        return new ParseResult(ParseResult.ResultOK, Arrays.copyOfRange(pb, length + 2, pb.length), unpack.getBKV());
    }

    private static void log(String content) {
        System.out.println(content);
    }

    private static byte[] concat(byte[]... arrays) {
        int length = 0;
        for (byte[] array : arrays) {
            length += array.length;
        }
        byte[] result = new byte[length];
        int pos = 0;
        for (byte[] array : arrays) {
            System.arraycopy(array, 0, result, pos, array.length);
            pos += array.length;
        }
        return result;
    }

    public static int indexOf(byte[] array, byte[] target) {
        if (target.length == 0) {
            return 0;
        }

        outer:
        for (int i = 0; i < array.length - target.length + 1; i++) {
            for (int j = 0; j < target.length; j++) {
            if (array[i + j] != target[j]) {
                continue outer;
            }
        }
            return i;
        }

        return -1;
    }

    private static byte calculateChecksum(byte[] buf) {
        int sum = 0;
        for (byte b : buf) {
            sum += Byte.toUnsignedInt(b);
        }
        return (byte) (sum & 0xFF);
    }
}

class ParseResult {
    public static final int ResultOK = 1;
    public static final int ResultInvalid = 2;
    public static final int ResultIncomplete = 3;
    public static final int ResultFatal = 4;

    public final int result;
    public final byte[] buf;
    public final BKV bkv;

    public ParseResult(int result, byte[] buf, BKV bkv) {
        this.result = result;
        this.buf = buf;
        this.bkv = bkv;
    }
}